function Designer(KOI, config) {
    $.extend(this, {
        config: $.extend(config,{
            layoutId: 'designer_layout',
            containerId: 'canvas_container',
            //canvasId: 'designer_canvas',
            viewportId: 'designer_viewport',
            shapesId: 'elements',
            panelItemWidth: 30,
            panelItemHeight: 30,
            canvasWidth: 1050,
            canvasHeight: 1050,
            pageMargin: 1000,
            anchorSize: 8,
            rotaterSize: 9,
            anchorColor: "#833",
            selectorColor: "#833",
            scale: 1
        }),

        _keyCode: {
            esc: 27,
            enter: 13
        },
        $layout: null,
        $container: null,
        $scope: null,
        $canvas: KOI.$canvas,
        $shapes: null,
        Mouse: {
            clientX: 0,
            clientY: 0,
            pageX: 0,
            pageY: 0,
            canvasX: 0,
            canvasY: 0,
            /* relative to canvas */
            canvas: function (e) {
                var canvas_offset = Designer.$canvas.offset();
                var canvas_x = canvas_offset.left - parseInt($(window).scrollLeft());
                var canvas_y = canvas_offset.top - parseInt($(window).scrollTop());
                return {
                    x: e.clientX - canvas_x,
                    y: e.clientY - canvas_y
                };
            },
            updateMouse: function (e) {
                Designer.Mouse.clientX = e.clientX;
                Designer.Mouse.clientY = e.clientY;
                Designer.Mouse.pageX = e.pageX;
                Designer.Mouse.pageY = e.pageY;
                var canvas_offset = Designer.$canvas.offset();
                var canvas_x = canvas_offset.left - parseInt($(window).scrollLeft());
                var canvas_y = canvas_offset.top - parseInt($(window).scrollTop());
                Designer.Mouse.canvasX = Designer.Mouse.clientX - canvas_x;
                Designer.Mouse.canvasY = Designer.Mouse.clientY - canvas_y;
                Designer.$scope.$digest();
                return Designer.Mouse;
            }
        },
        Popup: {
            $popups: $('#popups'),
            register: function (name, $trigger, tmplUrl, $target) {
                var $p = Designer.Popup.$popups.find('div.popup_container[name=' + name + ']');
                if ($p.length == 0) {
                    $p = $('<div/>').css({display: 'none'});
                    $p.load(tmplUrl, function () {
                        $(this).attr('_popup', 'false')
                            .attr('name', name).addClass('popup_container');
                        _angularCompile($(this), Designer.$scope).appendTo(Designer.Popup.$popups);
                        $trigger.click(function () {
                            Designer.Popup.toggle($p, $target);
                        });
                    });
                }
            },
            toggle: function ($origin, $target) {
                $target.children().each(function (i, elem) {
                    if (elem !== $origin.get(0)) {
                        $(elem).hide(function () {
                            $(this).attr('_popup', 'false')
                                .detach().appendTo(Designer.Popup.$popups);
                        });
                    }
                });
                if ($origin.attr('_popup') == 'true') {
                    $origin.attr('_popup', 'false');
                    $origin.hide(function () {
                        $origin.detach().appendTo(Designer.Popup.$popups);
                        $target.hide();
                    });
                }
                else {
                    $origin.attr('_popup', 'true');
                    $target.show(function () {
                        $origin.detach().appendTo($target);
                        $origin.show();
                        Designer.$scope.$digest();
                    });
                }
            }
        },
        Dock: {
            $container: $('#details').find('ul'),
            $popup: $('#right_popup'),
            docks: [],
            init: function () {
                this.register('element', 'fa fa-binoculars', '/app/tmpls/element_detail.html');
                this.register('properties', 'fa fa-list', '/app/tmpls/properties.html');
                this.register('debug', 'fa fa-code', '/app/tmpls/debug.html');
                this.render();
            },
            register: function (name, iconClass, tmplUrl) {
                var docks = Designer.Dock.docks;
                docks.push({
                    name: name,
                    icon: iconClass,
                    tmpl: tmplUrl
                });
            },
            render: function () {
                var $e = Designer.Dock.$container;
                var docks = Designer.Dock.docks;
                var $popup = Designer.Dock.$popup;
                $popup.hide();
                $e.empty();
                $(docks).each(function (i, elem) {
                    var $li = $('<li/>').append($('<a/>').append($('<i/>').addClass(elem.icon))
                        .attr('name', elem.name));
                    $e.append($li);
                    Designer.Popup.register(elem.name, $li, elem.tmpl, $popup);
                });
            }
        },
        Shape: {
            shapes: [],
            init: function () {
                //this.register('line', 'fa fa-gear', {type: 'l'});
                this.register('activity', 'fa fa-tasks', {type: 'a', name: 'new Act', width: 100, height: 80});
                this.register('synchronizer', 'fa fa-circle-o',
                    {type: 's', name: 'new Sync', width: 50, height: 50, condition: 'true'});
                this.render();
            },
            register: function (name, iconClass, options) {
                var shapes = Designer.Shape.shapes;
                shapes.push({
                    name: name,
                    icon: iconClass,
                    options: options
                });
            },
            render: function () {
                var $e = Designer.$shapes.find('ul');
                $e.empty();
                $(this.shapes).each(function (i, elem) {
                    var $li = $('<li/>').append($('<a/>').append($('<i/>').addClass(elem.icon))
                        .attr('name', elem.name)
                        .attr('title', elem.name)
                        .data('options', elem.options));
                    $e.append($li);
                });
            }
        },
        Menu: {},

        Canvas: {
            init: function () {
                var $canvas = Designer.$canvas;
                var $layout = Designer.$layout;

                $canvas.on('mousemove.hover', function (e) {
                    var mousePos = Designer.Mouse.canvas(e);
                    var p = KOI.findPoint({x: mousePos.x, y: mousePos.y});
                    if (p) {
                        KOI.hover(p);
                    }
                    else {
                        KOI.clearHover();
                    }
                    KOI.render();
                });

                $canvas.on('mousedown.dragging', Designer.Util.dragging($canvas,
                    function ($elem, event) {
                        var mousePos = Designer.Mouse.canvas(event);
                        var p = KOI.findPoint({x: mousePos.x, y: mousePos.y});
                        console.log('mousedown.dragging:find_p',p);
                        if (p) {
                            //p.changeColor("red");
                            event.data.p = p;
                            KOI.setCurrent(p);
                            Designer.$scope.current = p;
                            Designer.$scope.$digest();
                            KOI.render();
                        } else {
                            return false;
                        }
                    }, function ($elem, event, delta) {
                        var p = event.data.p;
                        KOI.g.move_point_to(p, p.x + delta.x , p.y + delta.y);
                        KOI.render();
                    }, function ($elem, event) {
                        //event.data.p.changeColor();
                        KOI.render();
                    }
                ));
                var handle = function (p) {

                    $canvas.css({'z-index': 999});
                    var overlay = $('');
                    KOI.hover(p, 'purple');
                    console.log(p);
                    KOI.render();
                    //let user to choose next
                    console.log('bind !!');
                    $canvas.unbind('click.selectEndPoint').bind('click.selectEndPoint', function (e) {
                        var next_mouse_pos = Designer.Mouse.canvas(e);
                        console.log('nextPos', next_mouse_pos);
                        var next_p = KOI.findPoint(next_mouse_pos);
                        //call the KOI to do the job
                        if (next_p && next_p !== p) {
                            //popup window to let user fill in the transition condition

                            KOI.hover(next_p, 'purple 4px');

                            var condition = 'true';
                            console.log('this', p);
                            console.log('next', next_p);
                            KOI.createEdge(p, next_p, {name: 'new', condition: condition});
                            KOI.render();
                        }
                        clean_and_quit();
                    });

                    $canvas.unbind('keyup.quitSelectEndPoint').bind('keyup.quitSelectEndPoint', function (e) {
                        console.log('esc pressed');
                        var code = e.keyCode ? e.keyCode : e.which;
                        if (code == Designer._keyCode.esc) {
                            clean_and_quit();
                        }
                    });

                };

                function clean_and_quit() {
                    $canvas.unbind('click.selectEndPoint');
                    $canvas.unbind('keyup.quitSelectEndPoint');
                    bindDblClick();
                    $hint_overlay.remove();
                    $layout.css({'z-index': 0});
                    //quit
                }

                var $hint_overlay = $('<div/>').text('Choose your next element.').css({
                    'z-index': 990,
                    position: 'fixed',
                    width: '100%',
                    height: '100%',
                    opacity: 0.9,
                    color: '#eee',
                    background: '#555',
                    'text-align': 'center',
                    'padding-top': '0.5em',
                    'font-size': '1.2em'
                });

                var bindDblClick = function () {
                    $canvas.unbind('dblclick.createEdge').bind('dblclick.createEdge', function (event) {
                        var mousePos = Designer.Mouse.canvas(event);
                        var p = KOI.findPoint(mousePos);
                        if (p) {
                            $hint_overlay.prependTo($('body'));
                            $layout.css({'z-index': 999});
                            handle(p);
                            unbindDblClick();
                        }
                        else console.log('please dblclick onto the point!');
                    });
                };
                var unbindDblClick = function () {
                    $canvas.unbind('dblclick.createEdge');
                };
                bindDblClick();
            }
        },

        initLayout: function () {

            $(window).bind("resize.designer", function () {
                var a = $(window).height() - $('header').outerHeight() - $('footer').outerHeight();

                Designer.$layout.height(a);
                //if ($("#demo_signup").length) {
                //    $("#designer_layout").height(a - $("#demo_signup").outerHeight())
                //}
            });
            $(window).trigger("resize.designer");

        },
        initHotKey: function () {
            var $layout = Designer.$layout;
            var $shapes = Designer.$shapes;
            var dragCanvas = this.Util.dragging($layout, function ($elem, event) {
                    $elem.css({
                        cursor: 'pointer'
                    })
                },
                function ($elem, event, delta) {
                    //console.log(delta);
                    var elem = $elem.get(0);
                    elem.scrollLeft = elem.scrollLeft - delta.x;
                    elem.scrollTop = elem.scrollTop - delta.y;
                }, function ($elem, event) {
                    $elem.css({
                        cursor: 'default'
                    })
                });

            //dragCanvas
            $layout.unbind('mousedown.dragCanvas').bind('mousedown.dragCanvas', function (e) {
                if (e.altKey) dragCanvas(e);
            });

            $shapes.on('mousedown.dragShape', 'li a', function (e) {
                Designer.Util.dragging($(this), function ($elem, event) {
                    console.log($elem);
                    //copy without event listeners
                    var p = event.data.$pseudoElem = $elem.clone(false, true);
                    var offset = $elem.offset();
                    p.css({
                        'z-index': 9999,
                        'position': 'fixed',
                        'top': offset.top + 'px',
                        'left': offset.left + 'px',
                        'color': '#006633',
                        'opacity': '0.5',
                        'font-size': '36px'
                    }).prependTo($('body'));
                }, function ($elem, event, delta) {
                    var offset = event.data.$pseudoElem.offset();
                    event.data.$pseudoElem.css({
                        left: offset.left + delta.x + 'px',
                        top: offset.top + delta.y + 'px'
                    });
                }, function ($elem, event) {
                    var mouseCanvas = Designer.Mouse.canvas(event);
                    if (mouseCanvas.x >= 0 && mouseCanvas.y >= 0) {
                        KOI.createPoint(mouseCanvas.x, mouseCanvas.y, $elem.data('options'));
                        KOI.render();
                    }
                    else {
                        alert('please drag element into the canvas.');
                        //
                    }
                    event.data.$pseudoElem.remove();
                })(e);
            });
        },
        init: function ($scope) {
            Designer.$scope = $scope;
            var layout = Designer.$layout = $("#" + Designer.config.layoutId);
            var container = Designer.$container = layout.find("#" + Designer.config.containerId);
            var canvas = Designer.$canvas;
            Designer.$shapes = $('#' + Designer.config.shapesId);
            container.css({
                width: Designer.config.canvasWidth,
                height: Designer.config.canvasHeight,
                padding: Designer.config.pageMargin
            });
            canvas.attr({
                width: Designer.config.canvasWidth,
                height: Designer.config.canvasHeight
            });

            var ctx = canvas.get(0).getContext('2d');
            ctx.fillStyle = '#fff'// 红
            ctx.fillRect(0, 0, 1000, 1000);
            ctx.strokeStyle = '#FFF';
            ctx.strokeRect(1, 1, 998, 998);

            Designer.initLayout();
            Designer.initHotKey();
            //Designer.initCanvas();
            Designer.centralize();

            Designer.Dock.init();
            Designer.Shape.init();
            Designer.Canvas.init();

            $(window).on('mousemove.updateMouse', function (e) {
                Designer.Mouse.updateMouse(e);
            });

            //the first render
            KOI.render();
            return this;
        },
        centralize: function () {
            var layout = $("#" + Designer.config.layoutId);
            layout.get(0).scrollLeft = Designer.config.pageMargin - 10;
            layout.get(0).scrollTop = Designer.config.pageMargin - 10;
        },
        open: function (process) {

        },
        save: function (process, url) {

        },
        Util: {
            dragging: function dragging($elem, onStart, onMoving, onEnd) {

                function prevent(event) {
                    event = event || window.event;
                    if (event.preventDefault) {
                        event.preventDefault();
                    }
                    else {
                        event.returnValue = false;
                    }
                }

                var onDragging = function (e) {
                    var delta = {
                        x: e.clientX - e.data._pos.x,
                        y: e.clientY - e.data._pos.y
                    };
                    (onMoving || function () {
                    })($elem, e, delta);
                    e.data._pos.x = e.clientX;
                    e.data._pos.y = e.clientY;
                    prevent(e);
                };

                var onDragStart = function (e) {
                    e.data = {
                        _pos: {
                            x: e.clientX,
                            y: e.clientY
                        }
                    };
                    var ret = (onStart || function () {
                    })($elem, e);
                    //jump out
                    if (ret !== undefined && ret === false) return;
                    //overlay.prependTo($('body'));
                    $(window).bind("mouseup", e.data, onDragEnd);
                    $(window).bind("mousemove", e.data, onDragging);
                    prevent(e);
                };

                var onDragEnd = function (e) {
                    $(window).unbind("mousemove", onDragging);
                    $(window).unbind("mouseup", onDragEnd);
                    (onEnd || function () {
                    })($elem, e);
                    //overlay.remove();
                    prevent(e);
                };

                return onDragStart;
            }
        }
    });
};
